package directory

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"mall/common/globalkey"
	"mall/service/attachment/model"
	"mall/service/attachment/rpc/internal/svc"
	"mall/service/attachment/rpc/internal/utils/errorhandler"
	"mall/service/attachment/rpc/types/attachment"
	"mall/service/backend/common/errorx"
	"mall/service/backend/common/i18n"

	"github.com/qiniu/go-sdk/v7/auth/qbox"
	"github.com/qiniu/go-sdk/v7/storage"
	"github.com/sirupsen/logrus"
	"github.com/volatiletech/null/v8"
	"github.com/zeromicro/go-zero/core/logx"
)

type BackendUpdateRecordLogic struct {
	ctx           context.Context
	svcCtx        *svc.ServiceContext
	bucketManager *storage.BucketManager
	bucket        string
	config        *storage.Config
	logx.Logger
}

func NewBackendUpdateRecordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BackendUpdateRecordLogic {
	return &BackendUpdateRecordLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *BackendUpdateRecordLogic) BackendUpdateRecord(in *attachment.RecordInfo) (*attachment.BaseResp, error) {
	if in.Id == nil || len(strings.TrimSpace(*in.Id)) == 0 {
		return nil, errorx.NewInvalidArgumentError(i18n.ValidationError)
	}

	var diagnose uint32
	if in.Diagnose != nil {
		diagnose = *in.Diagnose
	}

	if diagnose == 0 {
		return nil, errorx.NewInvalidArgumentError(i18n.ValidationError)
	}

	mac := qbox.NewMac(l.svcCtx.Config.QiniuConf.AccessKey, l.svcCtx.Config.QiniuConf.SecretKey)

	cfg := storage.Config{
		UseHTTPS:      l.svcCtx.Config.QiniuConf.UseHTTPS, // 是否使用https域名进行资源管理
		UseCdnDomains: l.svcCtx.Config.QiniuConf.UseCdnDomains, // 上传是否使用CDN上传加速
	}
	// 指定空间所在的区域，如果不指定将自动探测
	// 如果没有特殊需求，默认不需要指定
	// cfg.Region=&storage.ZoneHuabei
	switch l.svcCtx.Config.QiniuConf.Region {
	case "z2":
		cfg.Region = &storage.ZoneHuanan
	}

	l.bucketManager = storage.NewBucketManager(mac, &cfg)
	l.bucket = l.svcCtx.Config.QiniuConf.Bucket
	l.config = &cfg

	var err error
	switch diagnose {
	case 1: // 移动或重命名文件，如果目标文件存在，不强制覆盖，如果不覆盖，默认返回614 file exists
		err = l.qiniuMove(in, false)
	case 2: // 移动或重命名文件，如果目标文件存在，强制覆盖
		err = l.qiniuMove(in, true)
	case 3: // 复制文件副本，如果目标文件存在，不强制覆盖，如果不覆盖，默认返回614 file exists
		err = l.qiniuCopy(in, false)
	case 4: // 复制文件副本，如果目标文件存在，强制覆盖
		err = l.qiniuCopy(in, true)
	case 5: // 设置或更新文件的生存时间
		err = l.qiniuDeleteAfterDays(in)
	case 6: // 删除空间中的文件
		err = l.qiniuDelete(in)
	case 7: // 更新镜像空间中存储的文件内容
		err = l.qiniuPrefetch(in)
	case 29: // 查询数据处理请求状态
		err = l.qiniuPrefop(in)
	}
	// todo: CDN相关功能
	//   文件刷新
	//   目录刷新
	//   文件预取操作
	//   获取域名流量
	//   获取域名带宽
	//   获取日志下载链接
	//   构建时间戳防盗链访问链接 ***

	if err != nil {
		return nil, err
	}

	return &attachment.BaseResp{
		Msg: i18n.UpdateSuccess,
	}, nil
}

func (l *BackendUpdateRecordLogic) findInfo(in *attachment.RecordInfo) (*model.AttachmentFileInfo, error) {
	if i, err := strconv.ParseInt(strings.TrimSpace(*in.Id), 10, 64); err == nil && i > 0 {
		res, err := l.svcCtx.FileInfo.FindOne(l.ctx, nil, uint64(i))
		if err != nil {
			return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		if len(res.Bucket) > 0 {
			l.bucket = res.Bucket
		}

		return res, nil
	}

	return nil, nil
}

func (l *BackendUpdateRecordLogic) createOrUpdate(bucket string, key string, data *model.AttachmentFileInfo) error {
	fileInfo, err := l.bucketManager.Stat(bucket, key)
	if err != nil {
		logrus.Info(fmt.Sprintf("Qiniu Stat err: %+v", err))
		return err
	}

	if data != nil {
		data.KeyValue = key
		data.Bucket = bucket
		data.Size = uint64(fileInfo.Fsize)
		data.Hash = fileInfo.Hash
		data.MimeType = fileInfo.MimeType
		data.Type = uint8(fileInfo.Type)
		data.PutTime = uint64(fileInfo.PutTime)
		data.RestoreStatus = uint8(fileInfo.RestoreStatus)
		data.Status = uint8(fileInfo.Status)
		data.MD5 = fileInfo.Md5
		data.EndUser = fileInfo.EndUser
		data.Expiration = uint64(fileInfo.Expiration)
		data.TransitionToIa = uint64(fileInfo.TransitionToIA)
		data.TransitionToArchive = uint64(fileInfo.TransitionToArchive)
		data.TransitionToDeepArchive = uint64(fileInfo.TransitionToDeepArchive)
		if len(fileInfo.Parts) > 0 {
			var parts []string
			for _, partId := range fileInfo.Parts {
				parts = append(parts, strconv.FormatInt(partId, 10))
			}
			data.Parts = strings.Join(parts, ",")
		}
		if fileInfo.MetaData != nil {
			var items []string
			for mk, mv := range fileInfo.MetaData {
				items = append(items, mk + ":" + mv)
			}
			data.MetaValue = null.StringFrom(strings.Join(items, ";"))
		}

		err := l.svcCtx.FileInfo.Update(l.ctx, nil, data)
		if err != nil {
			return err
		}
	} else {
		var newData model.AttachmentFileInfo
		newData.KeyValue = key
		newData.Bucket = bucket
		newData.Size = uint64(fileInfo.Fsize)
		newData.Hash = fileInfo.Hash
		newData.MimeType = fileInfo.MimeType
		newData.Type = uint8(fileInfo.Type)
		newData.PutTime = uint64(fileInfo.PutTime)
		newData.RestoreStatus = uint8(fileInfo.RestoreStatus)
		newData.Status = uint8(fileInfo.Status)
		newData.MD5 = fileInfo.Md5
		newData.EndUser = fileInfo.EndUser
		newData.Expiration = uint64(fileInfo.Expiration)
		newData.TransitionToIa = uint64(fileInfo.TransitionToIA)
		newData.TransitionToArchive = uint64(fileInfo.TransitionToArchive)
		newData.TransitionToDeepArchive = uint64(fileInfo.TransitionToDeepArchive)
		if len(fileInfo.Parts) > 0 {
			var parts []string
			for _, partId := range fileInfo.Parts {
				parts = append(parts, strconv.FormatInt(partId, 10))
			}
			newData.Parts = strings.Join(parts, ",")
		}
		if fileInfo.MetaData != nil {
			var items []string
			for mk, mv := range fileInfo.MetaData {
				items = append(items, mk + ":" + mv)
			}
			newData.MetaValue = null.StringFrom(strings.Join(items, ";"))
		}

		err := l.svcCtx.FileInfo.Insert(l.ctx, nil, &newData)
		if err != nil {
			return err
		}
	}

	return nil
}

func (l *BackendUpdateRecordLogic) qiniuMove(in *attachment.RecordInfo, force bool) error {
	data, err := l.findInfo(in)
	if err != nil {
		return err
	}

	if data != nil {
		// 目标空间可以和源空间相同，但是不能为跨机房的空间
		var destBucket string
		// 目标文件名可以和源文件名相同，也可以不同
		var destKey string
		if in.Bucket != nil && len(strings.TrimSpace(*in.Bucket)) > 0 {
			destBucket = strings.TrimSpace(*in.Bucket)
		}
		if len(destBucket) == 0 {
			destBucket = l.bucket
		}
		if in.Key != nil && len(strings.TrimSpace(*in.Key)) > 0 {
			destKey = strings.TrimSpace(*in.Key)
		}
		if len(destKey) == 0 {
			destKey = data.KeyValue
		}
		if destBucket == l.bucket && destKey == data.KeyValue {
			return nil
		}

		err := l.bucketManager.Move(l.bucket, data.KeyValue, destBucket, destKey, force)
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu Move err: %+v", err))
			return errorx.NewAlreadyExistsError("file.keyAlreadyExists")
		}

		err = l.createOrUpdate(destBucket, destKey, data)
		if err != nil {
			return errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}

func (l *BackendUpdateRecordLogic) qiniuCopy(in *attachment.RecordInfo, force bool) error {
	data, err := l.findInfo(in)
	if err != nil {
		return err
	}

	if data != nil {
		// 目标空间可以和源空间相同，但是不能为跨机房的空间
		var destBucket string
		// 目标文件名可以和源文件名相同，也可以不同
		var destKey string
		if in.Bucket != nil && len(strings.TrimSpace(*in.Bucket)) > 0 {
			destBucket = strings.TrimSpace(*in.Bucket)
		}
		if len(destBucket) == 0 {
			destBucket = l.bucket
		}
		if in.Key != nil && len(strings.TrimSpace(*in.Key)) > 0 {
			destKey = strings.TrimSpace(*in.Key)
		}
		if len(destKey) == 0 {
			destKey = data.KeyValue
		}
		if destBucket == l.bucket && destKey == data.KeyValue {
			return nil
		}

		err := l.bucketManager.Copy(l.bucket, data.KeyValue, destBucket, destKey, force)
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu Copy err: %+v", err))
			return errorx.NewAlreadyExistsError("file.keyAlreadyExists")
		}

		err = l.createOrUpdate(destBucket, destKey, nil)
		if err != nil {
			return errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}

func (l *BackendUpdateRecordLogic) qiniuDeleteAfterDays(in *attachment.RecordInfo) error {
	data, err := l.findInfo(in)
	if err != nil {
		return err
	}

	if data != nil {
		var days int
		if in.Days != nil && *in.Days > 0 {
			days = int(*in.Days)
		} else {
			days = 7
		}

		err := l.bucketManager.DeleteAfterDays(l.bucket, data.KeyValue, days)
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu Copy err: %+v", err))
			return errorx.NewAlreadyExistsError("file.keyAlreadyExists")
		}

		data.DelStatus = uint8(globalkey.StatusDeleted)
		err = l.createOrUpdate(l.bucket, data.KeyValue, data)
		if err != nil {
			return errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}

func (l *BackendUpdateRecordLogic) qiniuDelete(in *attachment.RecordInfo) error {
	data, err := l.findInfo(in)
	if err != nil {
		return err
	}

	if data != nil {
		err := l.bucketManager.Delete(l.bucket, data.KeyValue)
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu Copy err: %+v", err))
			return errorx.NewAlreadyExistsError("file.keyAlreadyExists")
		}

		err = l.svcCtx.FileInfo.SoftDelete(l.ctx, nil, data.ID)
		if err != nil {
			return errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}

func (l *BackendUpdateRecordLogic) qiniuPrefetch(in *attachment.RecordInfo) error {
	data, err := l.findInfo(in)
	if err != nil {
		return err
	}

	if data != nil {
		var destBucket string
		if in.Bucket != nil && len(strings.TrimSpace(*in.Bucket)) > 0 {
			destBucket = strings.TrimSpace(*in.Bucket)
		}
		if len(destBucket) == 0 {
			destBucket = l.bucket
		}

		err := l.bucketManager.Prefetch(destBucket, data.KeyValue)
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu Prefetch err: %+v", err))
			return errorx.NewInternalError("file.updateFileFailed")
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}

func (l *BackendUpdateRecordLogic) qiniuPrefop(in *attachment.RecordInfo) error {
	data, err := l.findInfo(in)
	if err != nil {
		return err
	}

	if data != nil && len(data.PersistentID) > 0 {
		// 使用数据处理，首先需要构建一个OperationManager对象
		mac := qbox.NewMac(l.svcCtx.Config.QiniuConf.AccessKey, l.svcCtx.Config.QiniuConf.SecretKey)
		operationManager := storage.NewOperationManager(mac, l.config)

		ret, err := operationManager.Prefop(data.PersistentID)
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu Prefop err: %+v", err))
			return errorx.NewInternalError("file.updateFileFailed")
		}

		logrus.Info(fmt.Sprintf("Qiniu Prefop: %+v", ret))
		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}
